import hashlib
import json
import logging
import os
import uuid
from decimal import Decimal

import re
import requests
import xmltodict

from conf.config import BASE_DIR, DOMAIN
from conf.config import MP_KEY, MP_APP_ID, MP_MERCHANT_ID, MP_CERT, MP_CERT_KEY
from conf.logging import LOGGING_DICT_CONFIG
from util.network import get_local_public_ip

logger = logging.getLogger("payment")


def md5(text=None):
    """
    MD5加密
    :param text: 需要传入bytes类型,否则返回空串
    :return: 返回加密后的字符串
    """
    if text is None:
        return ''
    if isinstance(text, bytes):
        m = hashlib.md5()
        m.update(text)
        return m.hexdigest()
    return ''


def weixin_params_filter(params):
    """
    将微信非空参数按照参数名ASCII码从小到大排序（字典序）
    并拼接成url键值对的形式，并在最后添加key
    """
    results = ''
    if params is None or len(params) <= 0:
        return results
    ks = sorted(params)
    for k in ks:
        v = params[k]
        k = str(k)
        if v and v != '' and k != 'sign':
            results += '%s=%s&' % (k, v)
    return results + "key=" + MP_KEY


def weixin_verify(params):
    sign = params['sign']
    if sign:
        trade_type = params["trade_type"]
        # 根据trade_type来判断是app支付还是公众号支付（两种支付的校验方式是不一样的）
        if trade_type.upper() == "JSAPI":
            my_sign = WechatPay.sign(params)
        else:
            message = weixin_params_filter(params)
            my_sign = md5(message.encode()).upper()
        if sign == my_sign:
            return True
        else:
            return False
    else:
        return False


def weixin_verify_test_app():
    xmlstr = """
    <xml><appid><![CDATA[wx66bb762086a0a9db]]></appid><bank_type><![CDATA[ABC_DEBIT]]></bank_type><cash_fee><![CDATA[1]]></cash_fee><fee_type><![CDATA[CNY]]></fee_type><is_subscribe><![CDATA[N]]></is_subscribe><mch_id><![CDATA[1267836101]]></mch_id><nonce_str><![CDATA[26cd8ecadce0d4efd6cc8a8725cbd1f8]]></nonce_str><openid><![CDATA[o3rSQwy_fOUelJlTZBoeeKMK8CXI]]></openid><out_trade_no><![CDATA[14452185700182294]]></out_trade_no><result_code><![CDATA[SUCCESS]]></result_code><return_code><![CDATA[SUCCESS]]></return_code><sign><![CDATA[3AF4D41B67CC0F7F99A7A0F1C0D38A0C]]></sign><time_end><![CDATA[20151019093629]]></time_end><total_fee>1</total_fee><trade_type><![CDATA[APP]]></trade_type><transaction_id><![CDATA[1009410461201510191250839658]]></transaction_id></xml>
    """
    params = json.dumps(xmltodict.parse(xmlstr, encoding='utf-8'))
    params = json.loads(params)
    print(params)
    weixin_verify(params['xml'])


def weixin_verify_test_mp():
    xmlstr = """
   <xml>
    <appid><![CDATA[wx81d5fe3489231706]]></appid>
    <attach><![CDATA[傻老板微信公众号]]></attach>
    <bank_type><![CDATA[CFT]]></bank_type>
    <cash_fee><![CDATA[1]]></cash_fee>
    <device_info><![CDATA[WEB]]></device_info>
    <fee_type><![CDATA[CNY]]></fee_type>
    <is_subscribe><![CDATA[Y]]></is_subscribe>
    <mch_id><![CDATA[1295177901]]></mch_id>
    <nonce_str><![CDATA[87fb15c4]]></nonce_str>
    <openid><![CDATA[o5m4Js94JW6pnXNeL0UUwqfBOCcE]]></openid>
    <out_trade_no><![CDATA[14701090786017669]]></out_trade_no>
    <result_code><![CDATA[SUCCESS]]></result_code>
    <return_code><![CDATA[SUCCESS]]></return_code>
    <sign><![CDATA[5500AFE39BCD4DB8D54B3C51EA533254]]></sign>
    <time_end><![CDATA[20160802113833]]></time_end>
    <total_fee>1</total_fee>
    <trade_type><![CDATA[JSAPI]]></trade_type>
    <transaction_id><![CDATA[4005882001201608020358326234]]></transaction_id>
</xml>
   """
    params = json.dumps(xmltodict.parse(xmlstr, encoding='utf-8'))
    params = json.loads(params)
    trade_type = params["xml"]["trade_type"]
    sign = params["xml"]["sign"]
    print(sign)
    if trade_type == "JSAPI":
        rr = WechatPay.sign(params["xml"])
        print(rr)
    print(params)


class WechatPay(object):
    @staticmethod
    def generate_nonce_str():
        """
        生成随机字符串
        :return:
        """
        return str(uuid.uuid1()).split("-")[0]

    @staticmethod
    def sign(params):
        """
        生成签名
        """
        # 对各个不为空的键值对的参数名ASCII码从小到大排序（字典序）
        data = sorted(params.items(), key=lambda asd: asd[0], reverse=False)
        # 对参数值不为空的键值对拼接成URL键值对的格式（即key1=value1&key2=value2…）拼接成字符串stringA
        str_a = ""
        for item in data:
            k = item[0]
            v = item[1]
            if v and k != "sign":
                str_a += k + "=" + str(v) + "&"
        str_temp = str_a + "key=" + MP_KEY
        # 进行md5加密
        sign_value = md5(str_temp.encode()).upper()
        return sign_value

    @staticmethod
    def generate_dict_2_xml(dict_data):
        """
        生成xml数据
        :param dict_data:
        :return:
        """
        ret = "<xml>"
        for k, v in dict_data.items():
            ret += "<" + k + ">" + str(v) + "</" + k + ">"
        ret += "</xml>"
        return ret

    @staticmethod
    def order(out_trade_no, total_fee, description, client_ip, nonce_str, openid):
        """
        调用微信下单接口

        返回结果示例：
merchant_wechat
        <xml><return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[OK]]></return_msg>
        <appid><![CDATA[wx81d5fe3489231706]]></appid>
        <mch_id><![CDATA[1295177901]]></mch_id>
        <device_info><![CDATA[WEB]]></device_info>
        <nonce_str><![CDATA[QaG4kATUchR2U0CL]]></nonce_str>
        <sign><![CDATA[B5C0215F0F5D67CC58C0B91DBFA32F2C]]></sign>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <prepay_id><![CDATA[wx20160801104315a918692fee0264478378]]></prepay_id>
        <trade_type><![CDATA[JSAPI]]></trade_type>
        </xml>
        :return:
        """
        if not description:
            description = "智能设备"

        params = dict(
            appid=MP_APP_ID,
            mch_id=MP_MERCHANT_ID,
            device_info="WEB",
            nonce_str=nonce_str,
            sign=None,
            body=description,
            attach=f"{DOMAIN}微信公众号",
            out_trade_no=out_trade_no,
            total_fee=total_fee,
            spbill_create_ip=client_ip,
            notify_url=f"https://{DOMAIN}/payment/wechat_notify",
            trade_type="JSAPI",
            openid=openid
        )
        # 计算签名
        sign_value = WechatPay.sign(params)
        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)
        ret = requests.post(url="https://api.mch.weixin.qq.com/pay/unifiedorder", data=data.encode(),
                            timeout=30)

        result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
        if result_dict["return_code"] == "SUCCESS":
            return result_dict["prepay_id"]
        else:
            logging.error("ret:%s" % ret.content.decode("utf-8"))
            return None

    @staticmethod
    def store_order(out_trade_no, total_fee, description, client_ip, nonce_str, openid):
        """
        线下门店调用微信下单接口
        :param out_trade_no:
        :param total_fee:
        :param description:
        :param client_ip:
        :param nonce_str:
        :param openid:
        :return:
        """
        params = dict(
            appid=MP_APP_ID,
            mch_id=MP_MERCHANT_ID,
            device_info="WEB",
            nonce_str=nonce_str,
            sign=None,
            body=description,
            attach="style微信公众号",
            out_trade_no=out_trade_no,
            total_fee=total_fee,
            spbill_create_ip=client_ip,
            notify_url=f"https://{DOMAIN}/api/store/wechat_notify",
            trade_type="JSAPI",
            openid=openid
        )
        # 计算签名
        sign_value = WechatPay.sign(params)
        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)
        ret = requests.post(url="https://api.mch.weixin.qq.com/pay/unifiedorder", data=data.encode(),
                            timeout=30)

        result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
        if result_dict["return_code"] == "SUCCESS":
            return result_dict["prepay_id"]
        else:
            logging.error("ret:%s" % ret.content.decode("utf-8"))
            return None

    @staticmethod
    def pre_order(out_trade_no, total_fee, description, client_ip, nonce_str):
        """
        调用微信下单接口
        pc端扫码支付预交易订单生成
        :return:
        """
        params = dict(
            appid=MP_APP_ID,
            mch_id=MP_MERCHANT_ID,
            device_info="WEB",
            nonce_str=nonce_str,
            sign=None,
            body=description,
            out_trade_no=out_trade_no,
            total_fee=total_fee,
            spbill_create_ip=client_ip,
            notify_url="https://www.style999.com/payment",
            trade_type="NATIVE"
        )
        # 计算签名
        sign_value = WechatPay.sign(params)
        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)
        ret = requests.post(url="https://api.mch.weixin.qq.com/pay/unifiedorder", data=data.encode(),
                            timeout=30)

        result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
        if result_dict["return_code"] == "SUCCESS":
            if result_dict["result_code"] == "SUCCESS":
                return result_dict["code_url"]
            else:
                err_code_des = result_dict['err_code_des']
                logging.error("fail:%s" % err_code_des)
                return None
        else:
            logging.error("ret:%s" % ret.content.decode("utf-8"))
            return None

    @staticmethod
    def mp_order_query(out_trade_no, nonce_str):

        params = dict(
            appid=MP_APP_ID,
            mch_id=MP_MERCHANT_ID,
            nonce_str=nonce_str,
            sign=None,
            out_trade_no=out_trade_no,
        )
        # 计算签名
        sign_value = WechatPay.sign(params)
        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)
        ret = requests.post(url="https://api.mch.weixin.qq.com/pay/orderquery", data=data.encode(),
                            timeout=30)

        result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
        if result_dict["return_code"] == "SUCCESS" and result_dict["result_code"] == "SUCCESS":
            # logger.debug("ret:%s" % ret.content.decode("utf-8"))
            return result_dict
        else:
            return None

    @staticmethod
    def get_jsapi_pay_sign(time_stamp, nonce_str, package, sign_type):
        # appId, timeStamp, nonceStr, package, signType
        params = dict(
            appId=MP_APP_ID,
            timeStamp=time_stamp,
            nonceStr=nonce_str,
            package=package,
            signType=sign_type
        )
        sign_value = WechatPay.sign(params)
        return sign_value

    @staticmethod
    def order_refund(trade_no, pay_fee, op_user_id="system"):
        """
        微信订单退款
        :param trade_no:
        :param pay_fee:
        :param op_user_id:
        :return:
        <return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxdbfbd8e556ad48a0]]></appid>
<mch_id><![CDATA[1298515701]]></mch_id>
<nonce_str><![CDATA[G6RG9PtJ3R1hQ3H8]]></nonce_str>
<sign><![CDATA[4A77F63292808AC03CA090AEF16D3928]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<transaction_id><![CDATA[4005882001201612253886675055]]></transaction_id>
<out_trade_no><![CDATA[14826004807937942]]></out_trade_no>
<out_refund_no><![CDATA[14826004807937942]]></out_refund_no>
<refund_id><![CDATA[2005882001201612300696795754]]></refund_id>
<refund_channel><![CDATA[]]></refund_channel>
<refund_fee>1</refund_fee>
<coupon_refund_fee>0</coupon_refund_fee>
<total_fee>1</total_fee>
<cash_fee>1</cash_fee>
<coupon_refund_count>0</coupon_refund_count>
<cash_refund_fee>1</cash_refund_fee>
</xml>
        """
        nonce_str = WechatPay.generate_nonce_str()
        params = dict(
            appid=MP_APP_ID,
            mch_id=MP_MERCHANT_ID,
            nonce_str=nonce_str,
            sign=None,
            out_trade_no=trade_no,
            out_refund_no=trade_no,
            total_fee=int(pay_fee * 100),
            refund_fee=int(pay_fee * 100),
            op_user_id=op_user_id,
        )
        sign_value = WechatPay.sign(params)
        cert = MP_CERT
        key = MP_CERT_KEY

        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)

        try:
            cert_path = os.path.join(BASE_DIR, "wechat_cert.pem")
            f_cert = open(cert_path, "w")
            f_cert.write(cert)
            f_cert.close()

            key_path = os.path.join(BASE_DIR, "wechat_key.pem")
            f_key = open(key_path, "w")
            f_key.write(key)
            f_key.close()
            ret = requests.post(url="https://api.mch.weixin.qq.com/secapi/pay/refund", cert=(cert_path, key_path),
                                data=data.encode(),
                                timeout=30)
            logging.info("mp_order_refund:%s" % ret.content.decode("utf-8"))
            result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
            if not (result_dict["return_code"] == "SUCCESS" and result_dict["result_code"] == "SUCCESS"):
                return None
            return result_dict
        except Exception as e:
            logger.exception(e)
            return None

    @staticmethod
    def inc_pay(open_id, real_name, trade_no, fee, desc="提现付款"):
        """
        企业付款
        :param open_id:
        :param real_name:
        :param trade_no:  同一订单号多次调用时返回消息是成功的，但是实际上是不会付款的
        :param fee:
        :param desc:
        :return:
        """
        # 获取本机IP - 最多3次
        ip = get_local_public_ip()
        if not ip:
            ip = get_local_public_ip()
            if not ip:
                ip = get_local_public_ip()

        nonce_str = WechatPay.generate_nonce_str()
        params = dict(
            mch_appid=MP_APP_ID,
            mchid=MP_MERCHANT_ID,
            nonce_str=nonce_str,
            sign=None,
            partner_trade_no=trade_no,  # 商户订单号
            openid=open_id,
            check_name="FORCE_CHECK",
            re_user_name=real_name,
            amount=int(fee * 100),  # 付款金额，单位为分
            desc=desc,
            spbill_create_ip=ip,  # 调用接口的机器Ip地址
        )
        sign_value = WechatPay.sign(params)
        cert = MP_CERT
        key = MP_CERT_KEY

        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)

        try:
            cert_path = os.path.join(BASE_DIR, "wechat_cert.pem")
            f_cert = open(cert_path, "w")
            f_cert.write(cert)
            f_cert.close()

            key_path = os.path.join(BASE_DIR, "wechat_key.pem")
            f_key = open(key_path, "w")
            f_key.write(key)
            f_key.close()
            logger.debug("params=%s" % data)
            ret = requests.post(url="https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers",
                                cert=(cert_path, key_path),
                                data=data.encode(),
                                timeout=30)
            logger.info("inc_pay:%s" % ret.content.decode("utf-8"))
            result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
            if not (result_dict["return_code"] == "SUCCESS" and result_dict["result_code"] == "SUCCESS"):
                error_code = result_dict["err_code"]
                if error_code == "SYSTEMERROR":
                    # 主动查询接口，否则可能出现重复付款的问题
                    # 检查支付状态 status
                    inc_pay_query_ret = WechatPay.inc_pay_query(trade_no)
                    if not inc_pay_query_ret:
                        return None
                    inc_pay_query_status = inc_pay_query_ret["status"]
                    if inc_pay_query_status == "FAILED":
                        return None
                    return inc_pay_query_ret
                return None
            return result_dict
        except Exception as e:
            logger.exception(e)
        return None

    @staticmethod
    def inc_pay_query(trade_no):
        """
        企业付款查询
        :return:
        """
        nonce_str = WechatPay.generate_nonce_str()
        params = dict(
            nonce_str=nonce_str,
            sign=None,
            partner_trade_no=trade_no,  # 商户订单号
            mch_id=MP_MERCHANT_ID,
            appid=MP_APP_ID,
        )
        sign_value = WechatPay.sign(params)
        cert = MP_CERT
        key = MP_CERT_KEY

        params["sign"] = sign_value
        # 生成xml数据
        data = WechatPay.generate_dict_2_xml(params)

        try:
            cert_path = os.path.join(BASE_DIR, "wechat_cert.pem")
            f_cert = open(cert_path, "w")
            f_cert.write(cert)
            f_cert.close()

            key_path = os.path.join(BASE_DIR, "wechat_key.pem")
            f_key = open(key_path, "w")
            f_key.write(key)
            f_key.close()
            logger.debug("params=%s" % data)
            ret = requests.post(url="https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo",
                                cert=(cert_path, key_path),
                                data=data.encode(),
                                timeout=30)
            logger.info("inc_pay_query:%s" % ret.content.decode("utf-8"))
            result_dict = xmltodict.parse(ret.text, encoding='utf-8').get('xml', None)
            if not (result_dict["return_code"] == "SUCCESS" and result_dict["result_code"] == "SUCCESS"):
                return None
            return result_dict
        except Exception as e:
            logger.exception(e)
        return None


if __name__ == "__main__":
    import logging.config

    logging.config.dictConfig(LOGGING_DICT_CONFIG)
    openid = "o7qyl0kFoPaXIC4R8NXTZpobZ5o0"
    rn = "郑德位"
    no = "1"
    f = Decimal("1.00").quantize(Decimal("0.00"))
    # od = WechatPay.inc_pay(openid, rn, no, f)
    od = WechatPay.inc_pay_query(no)
    print(od)
    pass
